package com.adee.java.jdbc;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

/**
 * 测试同一个connection对象被多个线程同时使用所产生的问题
 * 1.获取到错误的自增主键id
 * 获取自增主键id的原理是，在同一个connection下，执行 select last_insert_id();
 * 会返回该connection下最近一次insert/update所对应的行记录的自增主键id的值，如果一个connection只由一个线程使用，
 * 则 select last_insert_id();返回的一定是最近一次insert/update的id值，但如果多个线程同时使用同一个connection，
 * 就会造成线程间获取到的id值不对，测试方法如下：
 * 线程1：                                               线程2：
 * insert into table_01 values (null, 'content01');
 *                                                      insert into table_01 values (null, 'content01');
 * select last_insert_id();
 *                                                      select last_insert_id();
 *
 * 如上测试，线程1和线程2返回的主键相同，因为同一个connection中的last_insert_id()只返回最有一次insert/update对应的主键id值。
 *
 * 2.测试多线程同时使用同一个connection时，事务提交混乱问题，不同线程会把其他线程的中间执行结果提交了。
 *
 * 结论：在使用mysql线程池时，一个connection对象在同一时刻只能由一个线程使用，当该线程使用完成后，把该connection对象归还池子后，
 * 该connection对象才能由其他线程获取使用。
 */
public class ConnectionConcurrencyTest {
    public static void main(String[] args) throws Exception {
        // test1();
        test2();
    }

    // 测试多线程同时使用同一个connection时主键id问题
    public static void test1() throws Exception {
        Connection conn = JdbcTest.getConnection();
        CountDownLatch latch = new CountDownLatch(2);
        new Thread(()->{
            try {
                // 先执行
                TimeUnit.SECONDS.sleep(1);
                PreparedStatement pst = conn.prepareStatement(
                        "insert into table_01 values (?, ?)");
                pst.setObject(1, null);
                pst.setObject(2, "content01");
                pst.executeUpdate();
                TimeUnit.SECONDS.sleep(5);
                ResultSet resultSet = pst.executeQuery("select last_insert_id()");
                if (resultSet.next()) {
                    long id = resultSet.getLong(1);
                    System.out.println("插入content01得到主键id：" + id);
                }

                latch.countDown();
            } catch (SQLException | InterruptedException e) {
                e.printStackTrace();
            }

        }).start();
        new Thread(()->{
            try {
                // 后执行
                TimeUnit.SECONDS.sleep(2);
                PreparedStatement pst = conn.prepareStatement(
                        "insert into table_01 values (?, ?)");
                pst.setObject(1, null);
                pst.setObject(2, "content02");
                pst.executeUpdate();
                TimeUnit.SECONDS.sleep(5);
                ResultSet resultSet = pst.executeQuery("select last_insert_id()");
                if (resultSet.next()) {
                    long id = resultSet.getLong(1);
                    System.out.println("插入content02得到主键id：" + id);
                }
                latch.countDown();
            } catch (SQLException | InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        latch.await();
    }


    // 测试多线程同时使用同一个connection时，事务提交混乱问题
    // https://blog.csdn.net/sunbo94/article/details/79409298
    public static void test2() throws Exception {
        Connection conn = JdbcTest.getConnection();
        conn.setAutoCommit(false);
        CountDownLatch latch = new CountDownLatch(2);
        new Thread(()->{
            try {
                // 先执行
                TimeUnit.SECONDS.sleep(1);
                PreparedStatement pst = conn.prepareStatement(
                        "insert into table_01 values (?, ?)");
                pst.setObject(1, null);
                pst.setObject(2, "content03");
                pst.executeUpdate();
                TimeUnit.SECONDS.sleep(5);
                conn.rollback();

                latch.countDown();
            } catch (SQLException | InterruptedException e) {
                e.printStackTrace();
            }

        }).start();
        new Thread(()->{
            try {
                // 后执行
                TimeUnit.SECONDS.sleep(2);
                PreparedStatement pst = conn.prepareStatement(
                        "insert into table_01 values (?, ?)");
                pst.setObject(1, null);
                pst.setObject(2, "content04");
                pst.executeUpdate();
                conn.commit(); // 此次提交会把线程1中执行的insert content03也提交成功
                TimeUnit.SECONDS.sleep(5);

                latch.countDown();
            } catch (SQLException | InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        latch.await();
    }
}
